/*******************************************************************************
* Copyright (c) 2015 Jeff Martin.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public
* License v3.0 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* Contributors:
* Jeff Martin - initial API and implementation
******************************************************************************/
package cuchaz.enigma.analysis;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import javassist.CannotCompileException;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.Descriptor;
import javassist.bytecode.EnclosingMethodAttribute;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.InnerClassesAttribute;
import javassist.expr.ConstructorCall;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
import javassist.expr.MethodCall;
import javassist.expr.NewExpr;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import cuchaz.enigma.Constants;
import cuchaz.enigma.bytecode.ClassRenamer;
import cuchaz.enigma.mapping.*;
public class JarIndex
{
private Set<ClassEntry> m_obfClassEntries;
private TranslationIndex m_translationIndex;
private Map<Entry, Access> m_access;
private Multimap<ClassEntry, FieldEntry> m_fields;
private Multimap<ClassEntry, BehaviorEntry> m_behaviors;
private Multimap<String, MethodEntry> m_methodImplementations;
private Multimap<BehaviorEntry, EntryReference<BehaviorEntry, BehaviorEntry>> m_behaviorReferences;
private Multimap<FieldEntry, EntryReference<FieldEntry, BehaviorEntry>> m_fieldReferences;
private Multimap<ClassEntry, ClassEntry> m_innerClassesByOuter;
private Map<ClassEntry, ClassEntry> m_outerClassesByInner;
private Map<ClassEntry, BehaviorEntry> m_anonymousClasses;
private Map<MethodEntry, MethodEntry> m_bridgedMethods;
public JarIndex()
{
m_obfClassEntries = Sets.newHashSet();
m_translationIndex = new TranslationIndex();
m_access = Maps.newHashMap();
m_fields = HashMultimap.create();
m_behaviors = HashMultimap.create();
m_methodImplementations = HashMultimap.create();
m_behaviorReferences = HashMultimap.create();
m_fieldReferences = HashMultimap.create();
m_innerClassesByOuter = HashMultimap.create();
m_outerClassesByInner = Maps.newHashMap();
m_anonymousClasses = Maps.newHashMap();
m_bridgedMethods = Maps.newHashMap();
}
public void indexJar(JarFile jar, boolean buildInnerClasses)
{
// step 1: read the class names
for(ClassEntry classEntry : JarClassIterator.getClassEntries(jar))
{
if(classEntry.isInDefaultPackage())
// move out of default package
classEntry =
new ClassEntry(Constants.NonePackage + "/"
+ classEntry.getName());
m_obfClassEntries.add(classEntry);
}
// step 2: index field/method/constructor access
for(CtClass c : JarClassIterator.classes(jar))
{
ClassRenamer.moveAllClassesOutOfDefaultPackage(c,
Constants.NonePackage);
for(CtField field : c.getDeclaredFields())
{
FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
m_access.put(fieldEntry, Access.get(field));
m_fields.put(fieldEntry.getClassEntry(), fieldEntry);
}
for(CtBehavior behavior : c.getDeclaredBehaviors())
{
BehaviorEntry behaviorEntry =
EntryFactory.getBehaviorEntry(behavior);
m_access.put(behaviorEntry, Access.get(behavior));
m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
}
}
// step 3: index extends, implements, fields, and methods
for(CtClass c : JarClassIterator.classes(jar))
{
ClassRenamer.moveAllClassesOutOfDefaultPackage(c,
Constants.NonePackage);
m_translationIndex.indexClass(c);
String className = Descriptor.toJvmName(c.getName());
for(String interfaceName : c.getClassFile().getInterfaces())
{
className = Descriptor.toJvmName(className);
interfaceName = Descriptor.toJvmName(interfaceName);
if(className.equals(interfaceName))
throw new IllegalArgumentException(
"Class cannot be its own interface! " + className);
}
for(CtBehavior behavior : c.getDeclaredBehaviors())
indexBehavior(behavior);
}
// step 4: index field, method, constructor references
for(CtClass c : JarClassIterator.classes(jar))
{
ClassRenamer.moveAllClassesOutOfDefaultPackage(c,
Constants.NonePackage);
for(CtBehavior behavior : c.getDeclaredBehaviors())
indexBehaviorReferences(behavior);
}
if(buildInnerClasses)
{
// step 5: index inner classes and anonymous classes
for(CtClass c : JarClassIterator.classes(jar))
{
ClassRenamer.moveAllClassesOutOfDefaultPackage(c,
Constants.NonePackage);
ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
ClassEntry outerClassEntry = findOuterClass(c);
if(outerClassEntry != null)
{
m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
boolean innerWasAdded =
m_outerClassesByInner.put(innerClassEntry,
outerClassEntry) == null;
assert innerWasAdded;
BehaviorEntry enclosingBehavior =
isAnonymousClass(c, outerClassEntry);
if(enclosingBehavior != null)
m_anonymousClasses.put(innerClassEntry,
enclosingBehavior);
else
{
// DEBUG
// System.out.println("INNER: " +
// outerClassEntry.getName() + "$" +
// innerClassEntry.getSimpleName());
}
}
}
// step 6: update other indices with inner class info
Map<String, String> renames = Maps.newHashMap();
for(ClassEntry innerClassEntry : m_innerClassesByOuter.values())
{
String newName =
innerClassEntry.buildClassEntry(
getObfClassChain(innerClassEntry)).getName();
if(!innerClassEntry.getName().equals(newName))
// DEBUG
// System.out.println("REPLACE: " +
// innerClassEntry.getName() + " WITH " + newName);
renames.put(innerClassEntry.getName(), newName);
}
EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
m_translationIndex.renameClasses(renames);
EntryRenamer.renameClassesInMultimap(renames,
m_methodImplementations);
EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
EntryRenamer.renameClassesInMap(renames, m_access);
}
}
private void indexBehavior(CtBehavior behavior)
{
// get the behavior entry
final BehaviorEntry behaviorEntry =
EntryFactory.getBehaviorEntry(behavior);
if(behaviorEntry instanceof MethodEntry)
{
MethodEntry methodEntry = (MethodEntry)behaviorEntry;
// index implementation
m_methodImplementations.put(behaviorEntry.getClassName(),
methodEntry);
// look for bridge and bridged methods
CtMethod bridgedMethod = getBridgedMethod((CtMethod)behavior);
if(bridgedMethod != null)
m_bridgedMethods.put(methodEntry,
EntryFactory.getMethodEntry(bridgedMethod));
}
// looks like we don't care about constructors here
}
private void indexBehaviorReferences(CtBehavior behavior)
{
// index method calls
final BehaviorEntry behaviorEntry =
EntryFactory.getBehaviorEntry(behavior);
try
{
behavior.instrument(new ExprEditor()
{
@Override
public void edit(MethodCall call)
{
MethodEntry calledMethodEntry =
EntryFactory.getMethodEntry(call);
ClassEntry resolvedClassEntry =
m_translationIndex.resolveEntryClass(calledMethodEntry);
if(resolvedClassEntry != null
&& !resolvedClassEntry.equals(calledMethodEntry
.getClassEntry()))
calledMethodEntry =
new MethodEntry(resolvedClassEntry,
calledMethodEntry.getName(), calledMethodEntry
.getSignature());
EntryReference<BehaviorEntry, BehaviorEntry> reference =
new EntryReference<BehaviorEntry, BehaviorEntry>(
calledMethodEntry, call.getMethodName(),
behaviorEntry);
m_behaviorReferences.put(calledMethodEntry, reference);
}
@Override
public void edit(FieldAccess call)
{
FieldEntry calledFieldEntry =
EntryFactory.getFieldEntry(call);
ClassEntry resolvedClassEntry =
m_translationIndex.resolveEntryClass(calledFieldEntry);
if(resolvedClassEntry != null
&& !resolvedClassEntry.equals(calledFieldEntry
.getClassEntry()))
calledFieldEntry =
new FieldEntry(calledFieldEntry, resolvedClassEntry);
EntryReference<FieldEntry, BehaviorEntry> reference =
new EntryReference<FieldEntry, BehaviorEntry>(
calledFieldEntry, call.getFieldName(),
behaviorEntry);
m_fieldReferences.put(calledFieldEntry, reference);
}
@Override
public void edit(ConstructorCall call)
{
ConstructorEntry calledConstructorEntry =
EntryFactory.getConstructorEntry(call);
EntryReference<BehaviorEntry, BehaviorEntry> reference =
new EntryReference<BehaviorEntry, BehaviorEntry>(
calledConstructorEntry, call.getMethodName(),
behaviorEntry);
m_behaviorReferences.put(calledConstructorEntry, reference);
}
@Override
public void edit(NewExpr call)
{
ConstructorEntry calledConstructorEntry =
EntryFactory.getConstructorEntry(call);
EntryReference<BehaviorEntry, BehaviorEntry> reference =
new EntryReference<BehaviorEntry, BehaviorEntry>(
calledConstructorEntry, call.getClassName(),
behaviorEntry);
m_behaviorReferences.put(calledConstructorEntry, reference);
}
});
}catch(CannotCompileException ex)
{
throw new Error(ex);
}
}
private CtMethod getBridgedMethod(CtMethod method)
{
// bridge methods just call another method, cast it to the return type,
// and return the result
// let's see if we can detect this scenario
// skip non-synthetic methods
if((method.getModifiers() & AccessFlag.SYNTHETIC) == 0)
return null;
// get all the called methods
final List<MethodCall> methodCalls = Lists.newArrayList();
try
{
method.instrument(new ExprEditor()
{
@Override
public void edit(MethodCall call)
{
methodCalls.add(call);
}
});
}catch(CannotCompileException ex)
{
// this is stupid... we're not even compiling anything
throw new Error(ex);
}
// is there just one?
if(methodCalls.size() != 1)
return null;
MethodCall call = methodCalls.get(0);
try
{
// we have a bridge method!
return call.getMethod();
}catch(NotFoundException ex)
{
// can't find the type? not a bridge method
return null;
}
}
private ClassEntry findOuterClass(CtClass c)
{
ClassEntry classEntry = EntryFactory.getClassEntry(c);
// does this class already have an outer class?
if(classEntry.isInnerClass())
return classEntry.getOuterClassEntry();
// inner classes:
// have constructors that can (illegally) set synthetic fields
// the outer class is the only class that calls constructors
// use the synthetic fields to find the synthetic constructors
for(CtConstructor constructor : c.getDeclaredConstructors())
{
Set<String> syntheticFieldTypes = Sets.newHashSet();
if(!isIllegalConstructor(syntheticFieldTypes, constructor))
continue;
ConstructorEntry constructorEntry =
EntryFactory.getConstructorEntry(constructor);
// gather the classes from the illegally-set synthetic fields
Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
for(String type : syntheticFieldTypes)
if(type.startsWith("L"))
{
ClassEntry outerClassEntry =
new ClassEntry(type.substring(1, type.length() - 1));
if(isSaneOuterClass(outerClassEntry, classEntry))
illegallySetClasses.add(outerClassEntry);
}
// who calls this constructor?
Set<ClassEntry> callerClasses = Sets.newHashSet();
for(EntryReference<BehaviorEntry, BehaviorEntry> reference : getBehaviorReferences(constructorEntry))
{
// make sure it's not a call to super
if(reference.entry instanceof ConstructorEntry
&& reference.context instanceof ConstructorEntry)
{
// is the entry a superclass of the context?
ClassEntry calledClassEntry =
reference.entry.getClassEntry();
ClassEntry superclassEntry =
m_translationIndex.getSuperclass(reference.context
.getClassEntry());
if(superclassEntry != null
&& superclassEntry.equals(calledClassEntry))
// it's a super call, skip
continue;
}
if(isSaneOuterClass(reference.context.getClassEntry(),
classEntry))
callerClasses.add(reference.context.getClassEntry());
}
// do we have an answer yet?
if(callerClasses.isEmpty())
{
if(illegallySetClasses.size() == 1)
return illegallySetClasses.iterator().next();
else
System.out
.println(String
.format(
"WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.",
classEntry));
}else if(callerClasses.size() == 1)
return callerClasses.iterator().next();
else
{
// multiple callers, do the illegally set classes narrow it
// down?
Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
intersection.retainAll(illegallySetClasses);
if(intersection.size() == 1)
return intersection.iterator().next();
else
System.out
.println(String
.format(
"WARNING: Unable to choose outer class for %s among options: %s",
classEntry, callerClasses));
}
}
return null;
}
private boolean isSaneOuterClass(ClassEntry outerClassEntry,
ClassEntry innerClassEntry)
{
// clearly this would be silly
if(outerClassEntry.equals(innerClassEntry))
return false;
// is the outer class in the jar?
if(!m_obfClassEntries.contains(outerClassEntry))
return false;
return true;
}
@SuppressWarnings("unchecked")
private boolean isIllegalConstructor(Set<String> syntheticFieldTypes,
CtConstructor constructor)
{
// illegal constructors only set synthetic member fields, then call
// super()
String className = constructor.getDeclaringClass().getName();
// collect all the field accesses, constructor calls, and method calls
final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
final List<ConstructorCall> constructorCalls = Lists.newArrayList();
try
{
constructor.instrument(new ExprEditor()
{
@Override
public void edit(FieldAccess fieldAccess)
{
if(fieldAccess.isWriter() && constructorCalls.isEmpty())
illegalFieldWrites.add(fieldAccess);
}
@Override
public void edit(ConstructorCall constructorCall)
{
constructorCalls.add(constructorCall);
}
});
}catch(CannotCompileException ex)
{
// we're not compiling anything... this is stupid
throw new Error(ex);
}
// are there any illegal field writes?
if(illegalFieldWrites.isEmpty())
return false;
// are all the writes to synthetic fields?
for(FieldAccess fieldWrite : illegalFieldWrites)
{
// all illegal writes have to be to the local class
if(!fieldWrite.getClassName().equals(className))
{
System.err.println(String.format(
"WARNING: illegal write to non-member field %s.%s",
fieldWrite.getClassName(), fieldWrite.getFieldName()));
return false;
}
// find the field
FieldInfo fieldInfo = null;
for(FieldInfo info : (List<FieldInfo>)constructor
.getDeclaringClass().getClassFile().getFields())
if(info.getName().equals(fieldWrite.getFieldName())
&& info.getDescriptor().equals(fieldWrite.getSignature()))
{
fieldInfo = info;
break;
}
if(fieldInfo == null)
// field is in a superclass or something, can't be a local
// synthetic member
return false;
// is this field synthetic?
boolean isSynthetic =
(fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
if(isSynthetic)
syntheticFieldTypes.add(fieldInfo.getDescriptor());
else
{
System.err.println(String.format(
"WARNING: illegal write to non synthetic field %s %s.%s",
fieldInfo.getDescriptor(), className, fieldInfo.getName()));
return false;
}
}
// we passed all the tests!
return true;
}
private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry)
{
// is this class already marked anonymous?
EnclosingMethodAttribute enclosingMethodAttribute =
(EnclosingMethodAttribute)c.getClassFile().getAttribute(
EnclosingMethodAttribute.tag);
if(enclosingMethodAttribute != null)
if(enclosingMethodAttribute.methodIndex() > 0)
return EntryFactory.getBehaviorEntry(
Descriptor.toJvmName(enclosingMethodAttribute.className()),
enclosingMethodAttribute.methodName(),
enclosingMethodAttribute.methodDescriptor());
else
// an attribute but no method? assume not anonymous
return null;
// if there's an inner class attribute, but not an enclosing method
// attribute, then it's not anonymous
InnerClassesAttribute innerClassesAttribute =
(InnerClassesAttribute)c.getClassFile().getAttribute(
InnerClassesAttribute.tag);
if(innerClassesAttribute != null)
return null;
ClassEntry innerClassEntry =
new ClassEntry(Descriptor.toJvmName(c.getName()));
// anonymous classes:
// can't be abstract
// have only one constructor
// it's called exactly once by the outer class
// the type the instance is assigned to can't be this type
// is abstract?
if(Modifier.isAbstract(c.getModifiers()))
return null;
// is there exactly one constructor?
if(c.getDeclaredConstructors().length != 1)
return null;
CtConstructor constructor = c.getDeclaredConstructors()[0];
// is this constructor called exactly once?
ConstructorEntry constructorEntry =
EntryFactory.getConstructorEntry(constructor);
Collection<EntryReference<BehaviorEntry, BehaviorEntry>> references =
getBehaviorReferences(constructorEntry);
if(references.size() != 1)
return null;
// does the caller use this type?
BehaviorEntry caller = references.iterator().next().context;
for(FieldEntry fieldEntry : getReferencedFields(caller))
if(fieldEntry.getType().hasClass()
&& fieldEntry.getType().getClassEntry().equals(innerClassEntry))
// caller references this type, so it can't be anonymous
return null;
for(BehaviorEntry behaviorEntry : getReferencedBehaviors(caller))
if(behaviorEntry.getSignature().hasClass(innerClassEntry))
return null;
return caller;
}
public Set<ClassEntry> getObfClassEntries()
{
return m_obfClassEntries;
}
public Collection<FieldEntry> getObfFieldEntries()
{
return m_fields.values();
}
public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry)
{
return m_fields.get(classEntry);
}
public Collection<BehaviorEntry> getObfBehaviorEntries()
{
return m_behaviors.values();
}
public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry)
{
return m_behaviors.get(classEntry);
}
public TranslationIndex getTranslationIndex()
{
return m_translationIndex;
}
public Access getAccess(Entry entry)
{
return m_access.get(entry);
}
public ClassInheritanceTreeNode getClassInheritance(
Translator deobfuscatingTranslator, ClassEntry obfClassEntry)
{
// get the root node
List<String> ancestry = Lists.newArrayList();
ancestry.add(obfClassEntry.getName());
for(ClassEntry classEntry : m_translationIndex
.getAncestry(obfClassEntry))
ancestry.add(classEntry.getName());
ClassInheritanceTreeNode rootNode =
new ClassInheritanceTreeNode(deobfuscatingTranslator,
ancestry.get(ancestry.size() - 1));
// expand all children recursively
rootNode.load(m_translationIndex, true);
return rootNode;
}
public ClassImplementationsTreeNode getClassImplementations(
Translator deobfuscatingTranslator, ClassEntry obfClassEntry)
{
// is this even an interface?
if(isInterface(obfClassEntry.getClassName()))
{
ClassImplementationsTreeNode node =
new ClassImplementationsTreeNode(deobfuscatingTranslator,
obfClassEntry);
node.load(this);
return node;
}
return null;
}
public MethodInheritanceTreeNode getMethodInheritance(
Translator deobfuscatingTranslator, MethodEntry obfMethodEntry)
{
// travel to the ancestor implementation
ClassEntry baseImplementationClassEntry =
obfMethodEntry.getClassEntry();
for(ClassEntry ancestorClassEntry : m_translationIndex
.getAncestry(obfMethodEntry.getClassEntry()))
{
MethodEntry ancestorMethodEntry =
new MethodEntry(new ClassEntry(ancestorClassEntry),
obfMethodEntry.getName(), obfMethodEntry.getSignature());
if(containsObfBehavior(ancestorMethodEntry))
baseImplementationClassEntry = ancestorClassEntry;
}
// make a root node at the base
MethodEntry methodEntry =
new MethodEntry(baseImplementationClassEntry,
obfMethodEntry.getName(), obfMethodEntry.getSignature());
MethodInheritanceTreeNode rootNode =
new MethodInheritanceTreeNode(deobfuscatingTranslator, methodEntry,
containsObfBehavior(methodEntry));
// expand the full tree
rootNode.load(this, true);
return rootNode;
}
public List<MethodImplementationsTreeNode> getMethodImplementations(
Translator deobfuscatingTranslator, MethodEntry obfMethodEntry)
{
List<MethodEntry> interfaceMethodEntries = Lists.newArrayList();
// is this method on an interface?
if(isInterface(obfMethodEntry.getClassName()))
interfaceMethodEntries.add(obfMethodEntry);
else
// get the interface class
for(ClassEntry interfaceEntry : getInterfaces(obfMethodEntry
.getClassName()))
{
// is this method defined in this interface?
MethodEntry methodInterface =
new MethodEntry(interfaceEntry, obfMethodEntry.getName(),
obfMethodEntry.getSignature());
if(containsObfBehavior(methodInterface))
interfaceMethodEntries.add(methodInterface);
}
List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
if(!interfaceMethodEntries.isEmpty())
for(MethodEntry interfaceMethodEntry : interfaceMethodEntries)
{
MethodImplementationsTreeNode node =
new MethodImplementationsTreeNode(deobfuscatingTranslator,
interfaceMethodEntry);
node.load(this);
nodes.add(node);
}
return nodes;
}
public Set<MethodEntry> getRelatedMethodImplementations(
MethodEntry obfMethodEntry)
{
Set<MethodEntry> methodEntries = Sets.newHashSet();
getRelatedMethodImplementations(methodEntries,
getMethodInheritance(null, obfMethodEntry));
return methodEntries;
}
private void getRelatedMethodImplementations(
Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node)
{
MethodEntry methodEntry = node.getMethodEntry();
if(containsObfBehavior(methodEntry))
// collect the entry
methodEntries.add(methodEntry);
// look at interface methods too
for(MethodImplementationsTreeNode implementationsNode : getMethodImplementations(
null, methodEntry))
getRelatedMethodImplementations(methodEntries, implementationsNode);
// recurse
for(int i = 0; i < node.getChildCount(); i++)
getRelatedMethodImplementations(methodEntries,
(MethodInheritanceTreeNode)node.getChildAt(i));
}
private void getRelatedMethodImplementations(
Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node)
{
MethodEntry methodEntry = node.getMethodEntry();
if(containsObfBehavior(methodEntry))
// collect the entry
methodEntries.add(methodEntry);
// recurse
for(int i = 0; i < node.getChildCount(); i++)
getRelatedMethodImplementations(methodEntries,
(MethodImplementationsTreeNode)node.getChildAt(i));
}
public Collection<EntryReference<FieldEntry, BehaviorEntry>> getFieldReferences(
FieldEntry fieldEntry)
{
return m_fieldReferences.get(fieldEntry);
}
public Collection<FieldEntry> getReferencedFields(
BehaviorEntry behaviorEntry)
{
// linear search is fast enough for now
Set<FieldEntry> fieldEntries = Sets.newHashSet();
for(EntryReference<FieldEntry, BehaviorEntry> reference : m_fieldReferences
.values())
if(reference.context == behaviorEntry)
fieldEntries.add(reference.entry);
return fieldEntries;
}
public Collection<EntryReference<BehaviorEntry, BehaviorEntry>> getBehaviorReferences(
BehaviorEntry behaviorEntry)
{
return m_behaviorReferences.get(behaviorEntry);
}
public Collection<BehaviorEntry> getReferencedBehaviors(
BehaviorEntry behaviorEntry)
{
// linear search is fast enough for now
Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
for(EntryReference<BehaviorEntry, BehaviorEntry> reference : m_behaviorReferences
.values())
if(reference.context == behaviorEntry)
behaviorEntries.add(reference.entry);
return behaviorEntries;
}
public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry)
{
return m_innerClassesByOuter.get(obfOuterClassEntry);
}
public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry)
{
return m_outerClassesByInner.get(obfInnerClassEntry);
}
public boolean isAnonymousClass(ClassEntry obfInnerClassEntry)
{
return m_anonymousClasses.containsKey(obfInnerClassEntry);
}
public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName)
{
return m_anonymousClasses.get(obfInnerClassName);
}
public Set<ClassEntry> getInterfaces(String className)
{
ClassEntry classEntry = new ClassEntry(className);
Set<ClassEntry> interfaces = new HashSet<ClassEntry>();
interfaces.addAll(m_translationIndex.getInterfaces(classEntry));
for(ClassEntry ancestor : m_translationIndex.getAncestry(classEntry))
interfaces.addAll(m_translationIndex.getInterfaces(ancestor));
return interfaces;
}
public Set<String> getImplementingClasses(String targetInterfaceName)
{
// linear search is fast enough for now
Set<String> classNames = Sets.newHashSet();
for(Map.Entry<ClassEntry, ClassEntry> entry : m_translationIndex
.getClassInterfaces())
{
ClassEntry classEntry = entry.getKey();
ClassEntry interfaceEntry = entry.getValue();
if(interfaceEntry.getName().equals(targetInterfaceName))
{
classNames.add(classEntry.getClassName());
m_translationIndex.getSubclassNamesRecursively(classNames,
classEntry);
}
}
return classNames;
}
public boolean isInterface(String className)
{
return m_translationIndex.isInterface(new ClassEntry(className));
}
public boolean containsObfClass(ClassEntry obfClassEntry)
{
return m_obfClassEntries.contains(obfClassEntry);
}
public boolean containsObfField(FieldEntry obfFieldEntry)
{
return m_access.containsKey(obfFieldEntry);
}
public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry)
{
return m_access.containsKey(obfBehaviorEntry);
}
public boolean containsObfArgument(ArgumentEntry obfArgumentEntry)
{
// check the behavior
if(!containsObfBehavior(obfArgumentEntry.getBehaviorEntry()))
return false;
// check the argument
if(obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry()
.getSignature().getArgumentTypes().size())
return false;
return true;
}
public boolean containsObfEntry(Entry obfEntry)
{
if(obfEntry instanceof ClassEntry)
return containsObfClass((ClassEntry)obfEntry);
else if(obfEntry instanceof FieldEntry)
return containsObfField((FieldEntry)obfEntry);
else if(obfEntry instanceof BehaviorEntry)
return containsObfBehavior((BehaviorEntry)obfEntry);
else if(obfEntry instanceof ArgumentEntry)
return containsObfArgument((ArgumentEntry)obfEntry);
else
throw new Error("Entry type not supported: "
+ obfEntry.getClass().getName());
}
public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry)
{
return m_bridgedMethods.get(bridgeMethodEntry);
}
public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry)
{
// build class chain in inner-to-outer order
List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
ClassEntry checkClassEntry = obfClassEntry;
while(true)
{
ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
if(obfOuterClassEntry != null)
{
obfClassChain.add(obfOuterClassEntry);
checkClassEntry = obfOuterClassEntry;
}else
break;
}
// switch to outer-to-inner order
Collections.reverse(obfClassChain);
return obfClassChain;
}
}